package org.jflame.commons.util;

import org.jflame.commons.convert.ConvertException;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.lang3.StringUtils;

/**
 * 数字类型工具类
 * 
 * @author charles.zhang
 */
public final class NumberHelper {

    /**
     * 构造新的BigDecimal对象
     * 
     * @param str 数字字符串
     * @return
     */
    public static final BigDecimal newDecimal(String str) {
        if (str == null) {
            return null;
        }
        if (StringUtils.isBlank(str)) {
            throw new NumberFormatException("A blank string is not a valid number");
        }
        return new BigDecimal(str);
    }

    /**
     * Number类型生成BigDecimal
     *
     * @param number 数字
     * @return BigDecimal
     * @throws NumberFormatException 转换异常
     */
    public static final BigDecimal newDecimal(Number number) {
        if (number instanceof BigDecimal) {
            return (BigDecimal) number;
        }
        return newDecimal(String.valueOf(number));
    }

    /**
     * 字符串转Integer,支持8,10,16进制字符格式
     * 
     * @param text 待转换字符串
     * @return Integer
     */
    public static Integer parseInt(String text) {
        return parseNumber(text, Integer.class);
    }

    /**
     * 字符串转long,支持8,10,16进制字符格式
     * 
     * @param text 待转换字符串
     * @return Long
     */
    public static Long parseLong(String text) {
        return parseNumber(text, Long.class);
    }

    /**
     * 字符串转Short,支持8,10,16进制字符格式
     * 
     * @param text 待转换字符串
     * @return Short
     */
    public static Short parseShort(String text) {
        return parseNumber(text, Short.class);
    }

    /**
     * 转换字符串到相应的数字类型,,支持8,10,16进制字符格式
     * 
     * @param text 待转换字符串
     * @param targetClass 数字类型
     * @return 相应的数字类型
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
        if (StringHelper.isEmpty(text)) {
            return null;
        }
        String trimmed = text.trim();
        if (Integer.class == targetClass || int.class == targetClass) {
            return (T) (hasHexPrefix(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
        } else if (Long.class == targetClass || long.class == targetClass) {
            return (T) (hasHexPrefix(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
        } else if (Byte.class == targetClass || byte.class == targetClass) {
            return (T) (hasHexPrefix(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
        } else if (Short.class == targetClass || short.class == targetClass) {
            return (T) (hasHexPrefix(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
        } else if (Float.class == targetClass || float.class == targetClass) {
            return (T) Float.valueOf(trimmed);
        } else if (Double.class == targetClass || double.class == targetClass) {
            return (T) Double.valueOf(trimmed);
        } else if (BigDecimal.class == targetClass || Number.class == targetClass) {
            return (T) new BigDecimal(trimmed);
        } else if (BigInteger.class == targetClass) {
            return (T) (hasHexPrefix(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
        } else {
            throw new ConvertException(
                    "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
        }
    }

    /**
     * 转为Number类型数据到具体的子类
     * 
     * @param number
     * @param targetClass
     * @return
     * @throws IllegalArgumentException
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T convertNumberToSubclass(Number number, Class<T> targetClass)
            throws IllegalArgumentException {

        if (targetClass.isInstance(number)) {
            return (T) number;
        } else if (Byte.class == targetClass || byte.class == targetClass) {
            long value = number.longValue();
            if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
                throw new IllegalArgumentException("值" + number + "超出范围" + targetClass.getName());
            }
            return (T) Byte.valueOf(number.byteValue());
        } else if (Short.class == targetClass || short.class == targetClass) {
            long value = number.longValue();
            if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
                throw new IllegalArgumentException("值" + number + "超出范围" + targetClass.getName());
            }
            return (T) Short.valueOf(number.shortValue());
        } else if (Integer.class == targetClass || int.class == targetClass) {
            long value = number.longValue();
            if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
                throw new IllegalArgumentException("值" + number + "超出范围" + targetClass.getName());
            }
            return (T) Integer.valueOf(number.intValue());
        } else if (Long.class == targetClass || long.class == targetClass) {
            BigInteger bigInt = null;
            if (number instanceof BigInteger) {
                bigInt = (BigInteger) number;
            } else if (number instanceof BigDecimal) {
                bigInt = ((BigDecimal) number).toBigInteger();
            }
            // Effectively analogous to JDK 8's BigInteger.longValueExact()
            BigInteger longMin = BigInteger.valueOf(Long.MIN_VALUE);
            BigInteger longMax = BigInteger.valueOf(Long.MAX_VALUE);
            if (bigInt != null && (bigInt.compareTo(longMin) < 0 || bigInt.compareTo(longMax) > 0)) {
                throw new IllegalArgumentException("值" + number + "超出范围" + targetClass.getName());
            }
            return (T) Long.valueOf(number.longValue());
        } else if (BigInteger.class == targetClass) {
            if (number instanceof BigDecimal) {
                // do not lose precision - use BigDecimal's own conversion
                return (T) ((BigDecimal) number).toBigInteger();
            } else {
                // original value is not a Big* number - use standard long conversion
                return (T) BigInteger.valueOf(number.longValue());
            }
        } else if (Float.class == targetClass || float.class == targetClass) {
            return (T) Float.valueOf(number.floatValue());
        } else if (Double.class == targetClass || double.class == targetClass) {
            return (T) Double.valueOf(number.doubleValue());
        } else if (BigDecimal.class == targetClass) {
            // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double)
            // (see BigDecimal javadoc for details)
            return (T) new BigDecimal(number.toString());
        } else {
            throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + number.getClass()
                    .getName() + "] to unsupported target class [" + targetClass.getName() + "]");
        }
    }

    /**
     * 检测给定字符串是否有16进制字符前缀标志
     * 
     * @param value
     */
    private static boolean hasHexPrefix(String value) {
        int index = (value.startsWith("-") ? 1 : 0);
        return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index));
    }

    /**
     * 转换字符串为BigInteger
     * <p>
     * 
     * @param value String
     * @see BigInteger#BigInteger(String, int)
     */
    public static BigInteger decodeBigInteger(String value) {
        int radix = 10;
        int index = 0;
        boolean negative = false;

        // Handle minus sign, if present.
        if (value.startsWith("-")) {
            negative = true;
            index++;
        }

        // Handle radix specifier, if present.
        if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
            index += 2;
            radix = 16;
        } else if (value.startsWith("#", index)) {
            index++;
            radix = 16;
        } else if (value.startsWith("0", index) && value.length() > 1 + index) {
            index++;
            radix = 8;
        }

        BigInteger result = new BigInteger(value.substring(index), radix);
        return (negative ? result.negate() : result);
    }

    public static boolean isNumber(Object value) {
        if (value == null) {
            throw new IllegalArgumentException("null value cannot determine whether it is a number type");
        }
        return isNumberType(value.getClass());
    }

    /**
     * 判断给定的Class是不是数字类型,即Number子类或基础数字类型
     * 
     * @param clazz
     * @return
     */
    public static boolean isNumberType(Class<?> clazz) {
        if (Number.class.isAssignableFrom(clazz)) {
            return true;
        }
        return isPrimitive(clazz);
    }

    /**
     * 判断是否是一个数字基类
     * 
     * @param clazz
     * @return
     */
    public static boolean isPrimitive(Class<?> clazz) {
        return clazz == Byte.TYPE || clazz == Short.TYPE || clazz == Integer.TYPE || clazz == Long.TYPE
                || clazz == Float.TYPE || clazz == Double.TYPE;
    }

    /**
     * 判断一个Integer数字大于0
     * 
     * @param num
     * @return 大于0返回true,null或小于0返回false
     */
    public static boolean gtZero(Integer num) {
        return num != null && num > 0;
    }

    /**
     * 判断一个Integer数字是否是null或0
     * 
     * @param num
     * @return
     */
    public static boolean isNullOrZero(Integer num) {
        return num == null || num == 0;
    }

    /**
     * 判断一个Integer数字是否是非空且非0数字
     * 
     * @param num
     * @return
     */
    public static boolean isNotNullAndZero(Integer num) {
        return num != null && num != 0;
    }

    /**
     * 判断一个Long数字大于0
     * 
     * @param num
     * @return 大于0返回true,null或小于0返回false
     */
    public static boolean gtZero(Long num) {
        return num != null && num > 0;
    }

    /**
     * 判断是否大于0的数,且不等于null
     * 
     * @param decimal
     * @return
     */
    public static boolean isPositive(BigDecimal decimal) {
        return decimal != null && decimal.signum() == 1;
    }

    /**
     * 判断是否不等于0,且不等于null
     * 
     * @param decimal
     * @return
     */
    public static boolean isNotZero(BigDecimal decimal) {
        return decimal != null && decimal.signum() != 0;
    }

    /**
     * 小于
     * 
     * @param num1
     * @param num2
     * @return true=num1&lt;num2
     */
    public static boolean lt(BigDecimal num1, BigDecimal num2) {
        return num1.compareTo(num2) == -1;
    }

    /**
     * 小于等于
     * 
     * @param num1
     * @param num2
     * @return true=num1&lt;=num2
     */
    public static boolean le(BigDecimal num1, BigDecimal num2) {
        return num1.compareTo(num2) <= 0;
    }

    /**
     * 大于
     * 
     * @param num1
     * @param num2
     * @return true=num1&gt;num2
     */
    public static boolean gt(BigDecimal num1, BigDecimal num2) {
        return num1.compareTo(num2) == 1;
    }

    /**
     * 大于等于
     * 
     * @param num1
     * @param num2
     * @return true=num1&gt;=num2
     */
    public static boolean ge(BigDecimal num1, BigDecimal num2) {
        return num1.compareTo(num2) >= 0;
    }

    /**
     * 等于
     * 
     * @param num1
     * @param num2
     * @return true=num1==num2
     */
    public static boolean eq(BigDecimal num1, BigDecimal num2) {
        return (num1 == null && num2 == null) || num1.compareTo(num2) == 0;
    }

    /**
     * 判断Double类型不为null且大于0
     * 
     * @param i
     * @return
     */
    public static boolean gtZero(Double i) {
        return i != null && i > 0;
    }

    /**
     * 判断一个BigDecimal数字大于0
     * 
     * @param num
     * @return 大于0返回true,null或小于0返回false
     */
    public static boolean gtZero(BigDecimal num) {
        return num != null && num.signum() > 0;
    }

    /**
     * 判断一个BigDecimal数字是否是等于0
     * 
     * @param num
     * @return
     */
    public static boolean isZero(BigDecimal num) {
        return num != null && num.compareTo(BigDecimal.ZERO) == 0;
    }

    /**
     * 判断字符是否是一个数字(0-9)
     * 
     * @param c
     * @return
     */
    public static boolean isLatinDigits(char c) {
        return c >= '0' && c <= '9';
    }

    /**
     * 生成不重复的随机数字
     * 
     * @param size 要生成个数
     * @param origin 最小数字
     * @param bound 最大数字(不包含)
     * @return
     */
    public static int[] randomInts(int size, int origin, int bound) {
        Set<Integer> tmpSet = new HashSet<>(size);
        int[] ints = new int[size];
        ThreadLocalRandom random = ThreadLocalRandom.current();
        while (tmpSet.size() < size) {
            tmpSet.add(random.nextInt(origin, bound));
        }
        int i = 0;
        for (Integer num : tmpSet) {
            ints[i++] = num;
        }
        return ints;
    }

}
